import fs from 'fs'
import Paths from '../../utils/entity/Paths'
import Names from './Names'
import Column from './Column'
import DataType from './DataType'
import BaseAdapter from '../adapter/jpa/BaseAdapter'
import SqlBaseAdapter from '../adapter/sql/BaseAdapter'

/**
 * 数据库表信息
 */
export default class Table {
  /**
   * 表信息，通过 SHOW TABLE STATUS FROM #{databaseName} 获取到的参数
   * @param Name {string} 表名
   * @param Engine {string} 存储引擎
   * @param Collation {string} 字符集
   * @param Comment {string} 表说明
   * @param model {string} 模板引擎模式，可选值：[JPA, SQL]
   */
  constructor({
                Name,
                Engine,
                Collation,
                Comment
              }, model = 'JPA') {
    /**
     * JPA 模板引擎
     * @type {boolean}
     */
    this.isJpaModel = model === 'JPA'
    /**
     * SQL 模板引擎
     * @type {boolean}
     */
    this.isSqlModel = model === 'SQL'
    /**
     * 模板引擎名称转小写用于包名
     * @type {string}
     */
    this.modeNameLowerCase = model.toLowerCase()
    /**
     * 表名
     * @type {string}
     */
    this.name = Name
    /**
     * 存储引擎
     * @return {string}
     */
    this.engine = Engine
    /**
     * 默认字符集
     * @return {string}
     */
    this.collation = Collation
    /**
     * 表说明
     * @return {string}
     */
    this.comment = Comment.replace(/\s/g, ' ').replace(/"/g, '\'')
    /**
     * 基于表名生成各种命名
     * @type {Names}
     */
    this.names = new Names(this.name)
    /**
     * 当前日期
     * @type {string}
     */
    this.date = new Date().formatDate()
    /**
     * 更新排除实体属性名
     * @type {string[]}
     */
    this.updateExcludeColumnNames = ['id', 'deleted', 'createTime', 'createUserId', 'updateTime']
    /**
     * 测试保存排除实体属性名
     * @type {string[]}
     */
    this.saveTestExcludeColumnNames = ['id', 'deleted', 'createTime', 'createUserId', 'updateUserId', 'updateTime']
    /**
     * 查询排除实体属性名
     * @type {string[]}
     */
    this.queryExcludeColumnNames = ['id', 'createTime', 'updateTime', 'createUserName', 'updateUserName']
    if (this.isJpaModel) {
      this.setAdapters([new BaseAdapter()])
    } else if (this.isSqlModel) {
      this.setAdapters([new SqlBaseAdapter()])
    }
  }

  /**
   * 设置作者姓名
   * @param author {string} 作者姓名
   * @result {Table}
   */
  setAuthor(author) {
    /**
     * 作者姓名
     * @type {string}
     */
    this.author = author
    return this
  }

  /**
   * 设置基础包名
   * @param pkg {string} 包名
   * @result {Table}
   */
  setPkg(pkg) {
    /**
     * 基础包名
     * @type {string}
     */
    this.pkg = pkg
    return this
  }

  /**
   * 设置列属性
   * @param columns {Object[]} 列集合
   * @result {Table}
   */
  setColumns(columns = []) {
    /**
     * 列属性
     * @return {Column[]}
     */
    this.columns = columns.map((obj, index) => {
      obj.index = index
      return new Column(obj)
    })
    /**
     * 列名集合
     * @type {string[]}
     */
    this.columnNames = this.columns.map(col => col.name)
    /**
     * 主键数据类型
     * @type {string}
     */
    this.idType = (() => {
      // 目前只支持 Long 、 String 类型作为ID
      const dataType = (this.columns.find(({name}) => this.isIdColumn(name)) || {dataType: {}}).dataType
      return dataType.java === DataType.VARCHAR.java ? dataType.java : DataType.BIGINT.java
    })()
    /**
     * 主键数据类型
     * @type {string}
     */
    this.jsIdType = (() => {
      // 目前只支持 Long 、 String 类型作为ID
      const dataType = (this.columns.find(({name}) => this.isIdColumn(name)) || {dataType: {}}).dataType
      return dataType.java === DataType.VARCHAR.java ? dataType.javascript : DataType.BIGINT.javascript
    })()
    /**
     * 表中是否存在 createUserId 字段
     * @type {boolean}
     */
    this.existCreateUserIdColumn = this.columns.some(({name}) => this.isCreateUserIdColumn(name))
    /**
     * 表中是否存在 createTime 字段
     * @type {boolean}
     */
    this.existCreateTimeColumn = this.columns.some(({name}) => this.isCreateTimeColumn(name))
    /**
     * 表中是否存在 updateUserId 字段
     * @type {boolean}
     */
    this.existUpdateUserIdColumn = this.columns.some(({name}) => this.isUpdateUserIdColumn(name))
    /**
     * 表中是否存在 updateTime 字段
     * @type {boolean}
     */
    this.existUpdateTimeColumn = this.columns.some(({name}) => this.isUpdateTimeColumn(name))
    /**
     * 表中是否存在 deleted 字段
     * @type {boolean}
     */
    this.existDeletedColumn = this.columns.some(({name}) => this.isDeleteColumn(name))
    /**
     * insert 设置默认值
     */
    this.insertOptionalDefault = this.columns
      .filter(col => !this.saveTestExcludeColumnNames.includes(col.name))
      .map(col => {
        // console.log(['insertOptionalDefault', col.name, col.defaultValue, col.notNullAndNotDefault, col.notNull])
        if (col.dataType.java === DataType.VARCHAR.java) {
          return `this.${col.name}=Optional.ofNullable(this.${col.name}).orElse("${col.defaultValue || ''}"); // ${col.comment}`
        }
        let setDefaultValue = true
        if (col.notNullAndNotDefault) {// 列没有默认值
          setDefaultValue = false
        }
        if (!col.notNull) { // 列允许空
          setDefaultValue = false
        }
        switch (col.dataType.java) {
          case 'Byte':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse((byte) ${parseInt(col.defaultValue || '0')}); // ${col.comment}`
          case 'Short':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse((short) ${parseInt(col.defaultValue || '0')}); // ${col.comment}`
          case 'Integer':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse(${parseInt(col.defaultValue || '0')}); // ${col.comment}`
          case 'Long':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse(${parseInt(col.defaultValue || '0')}L); // ${col.comment}`
          case 'Double':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse(${parseFloat(col.defaultValue || '0')}D); // ${col.comment}`
          case 'Float':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse(${parseFloat(col.defaultValue || '0')}F); // ${col.comment}`
          case 'BigDecimal':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse(new BigDecimal("${col.defaultValue || '0'}))); // ${col.comment}`
          case 'Date':
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse(Dates.now().date))); // ${col.comment}`
          default:
            return `${setDefaultValue ? '' : '// '}this.${col.name}=Optional.ofNullable(this.${col.name}).orElse(null); // ${col.comment}`
        }
      })
      .filter(Boolean)
      .join('\n        ')
    return this
  }

  /**
   * 设置特定字段或枚举字段适配策略
   * @param adapters {BaseAdapter[]} 自定义枚举字段适配策略
   * @result {Table}
   */
  setAdapters(adapters = [new BaseAdapter()]) {
    /**
     * 自定义枚举字段适配策略
     * @return {BaseAdapter[]} 自定义枚举字段适配策略
     */
    this.adapters = adapters
    return this
  }

  /**
   * 设置输出包目录
   * @param module {string} 模块名
   * @result {Table}
   */
  setOutput(module) {
    /**
     * 输出路径
     * @type {string}
     */
    this.output = `${module}/src/test/java/${this.pkg.replace(/\./g, '/')}`
    return this
  }

  /**
   * 序号增量
   * @returns {number} 返回增量后的序号
   */
  incrementAndGetOrdered() {
    this.ordered += 1
    return this.ordered
  }

  /**
   * 是否包含 id 字段
   * @type {boolean}
   */
  isIdColumn(name) {
    return name === 'id'
  }

  /**
   * 是否包含 createUserId 字段
   * @type {boolean}
   */
  isCreateUserIdColumn(name) {
    return name === 'createUserId'
  }

  /**
   * 是否包含 createTime 字段
   * @type {boolean}
   */
  isCreateTimeColumn(name) {
    return name === 'createTime'
  }

  /**
   * 是否包含 updateUserId 字段
   * @type {boolean}
   */
  isUpdateUserIdColumn(name) {
    return name === 'updateUserId'
  }

  /**
   * 是否包含 updateTime 字段
   * @type {boolean}
   */
  isUpdateTimeColumn(name) {
    return name === 'updateTime'
  }

  /**
   * 是否包含 deleted 字段
   * @type {boolean}
   */
  isDeleteColumn(name) {
    return name === 'deleted'
  }

  /**
   * 是否包含 createUserId、 updateUserId 字段
   * @type {boolean}
   */
  isIncludeUserIdColumn(name) {
    return this.isCreateUserIdColumn(name) || this.isUpdateUserIdColumn(name)
  }

  /**
   * id 字段数据类型是否为 "数字" 类型
   * @returns {boolean}
   */
  isIdNumber() {
    return [DataType.BIGINT.java, DataType.INT.java].includes(this.idType)
  }

  /**
   * id 字段数据类型是否为 "字符" 类型
   * @returns {boolean}
   */
  isIdString() {
    // return [DataType.VARCHAR.java, DataType.CHAR.java].includes(this.idType)
    return DataType.CHAR.java === this.idType
  }

  /**
   * 获取分页查询包含的字段
   * @return {string}
   */
  getQueryIncludeColumnNames() {
    const excludes = [
      DataType.DECIMAL.mysql,
      DataType.DOUBLE.mysql,
      DataType.FLOAT.mysql,
      DataType.TEXT.mysql,
      DataType.MEDIUMTEXT.mysql,
      DataType.LONGTEXT.mysql,
      DataType.DATE.mysql,
      DataType.TIMESTAMP.mysql,
      DataType.DATETIME.mysql,
      DataType.JSON.mysql
    ]
    return this.columns
      .filter(col => !excludes.includes(col.dataType.mysql))
      .map(({name}) => this.queryExcludeColumnNames.includes(name) ? null : name)
      .filter(Boolean)
      .join('", "')
  }

  /**
   * 获取新增、修改包含的字段
   * @return {string}
   */
  getInsertIncludeColumnNames() {
    return this.columns
      .map(({name}) => this.saveTestExcludeColumnNames.includes(name) ? null : name)
      .filter(Boolean)
      .map(name => `body.${name}`)
      .join('", "')
  }

  /**
   * service
   * @param auth {boolean} 接口是否需要鉴权
   * @return {string}
   */
  getAuthUserValid(auth) {
    return auth ? ',@NotNull(message = "【userId】不能为null") final Long userId' : ''
  }

  /**
   * service
   * 获取 id 校验规则
   * @return {string}
   */
  getIdValid() {
    if (this.isIdNumber()) {
      return `@NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final ${this.idType} id`
    } else if (this.isIdString()) {
      return `@NotBlank(message = "【id】不能为空") final ${this.idType} id`
    }
    return `@NotNull(message = "【id】不能为null") final ${this.idType} id`
  }

  /**
   * get 方法校验入参
   * @param excludeColumnNames {string[]}
   * @returns {string}
   */
  validateGetColumn(excludeColumnNames) {
    const excludes = excludeColumnNames || this.saveTestExcludeColumnNames
    return this.columns
      .filter(col => !excludes.includes(col.name))
      .filter(col => col.notNull)
      .map(col => `
    ${col.dataType.java === DataType.VARCHAR.java ? '@NotBlank' : '@NotNull'} 
    @Override
    public ${col.dataType.java} ${col.nameGet}() {
        return super.${col.nameGet}();
    }`)
      .join('\n') || ''
  }

  /**
   * 前端清除数据类型不匹配的参数
   * @returns {string}
   */
  clearBlankValue() {
    return this.columns
      .filter(col => DataType.VARCHAR.java !== col.dataType.java)
      .map(col => `if(obj.${col.name} === '') delete obj.${col.name}`)
      .join('\n    ')
  }

  /**
   * 写入实体
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeEntity(templateName) {
    { // Entity
      const {Entity} = await import(`../${templateName}`)
      const filename = `business/${this.names.pkgName}/entity/${this.names.TabName}.java`
      const absolute = Paths.resolve(this.output, filename).mkdirsParent().absolute()
      console.log(absolute)
      const content = await Entity(this)
      fs.writeFileSync(absolute, content
        .replace(/[/]+ <-/g, '')
        .replace(/\n{3,}/g, '\n\n')
      )
    }
    { // InsertDTO
      const {InsertDTO} = await import(`../${templateName}`)
      const filename = `business/${this.names.pkgName}/dto/${this.names.TabName}InsertDTO.java`
      const absolute = Paths.resolve(this.output, filename).mkdirsParent().absolute()
      console.log(absolute)
      const content = await InsertDTO(this)
      if (content) {
        fs.writeFileSync(absolute, content
          .replace(/[/]+ <-/g, '')
          .replace(/\n{3,}/g, '\n\n')
        )
      }
    }
    { // UpdateDTO
      const {UpdateDTO} = await import(`../${templateName}`)
      const filename = `business/${this.names.pkgName}/dto/${this.names.TabName}UpdateDTO.java`
      const absolute = Paths.resolve(this.output, filename).mkdirsParent().absolute()
      console.log(absolute)
      const content = await UpdateDTO(this)
      if (content) {
        fs.writeFileSync(absolute, content
          .replace(/[/]+ <-/g, '')
          .replace(/\n{3,}/g, '\n\n')
        )
      }
    }
    return this
  }

  /**
   * 写入 http 文件
   *
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeHttp(templateName) {
    const {Http} = await import(`../${templateName}`)
    const output = this.output.replace(/(\/src\/test\/java)\/.*$/, '$1')
    const filename = `${templateName.startsWith('Open') ? 'Open' : ''}${this.names.JavaName}Controller.http`
    const absolute = Paths.resolve(output, filename).mkdirsParent().absolute()
    console.log(absolute)
    const content = await Http(this)
    fs.writeFileSync(absolute, content
      .replace(/[/]+ <-/g, '')
      .replace(/\n{3,}/g, '\n\n')
    )
    return this
  }

  /**
   * 写入 Controller
   *
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeController(templateName) {
    /**
     * 接口排序序号
     * @type {number}
     */
    this.ordered = 0
    const {Controller} = await import(`../${templateName}`)
    const filename = `business/${this.names.pkgName}/controller/${templateName.startsWith('Open') ? 'Open' : ''}${this.names.JavaName}Controller.java`
    const absolute = Paths.resolve(this.output, filename).mkdirsParent().absolute()
    console.log(absolute)
    const content = await Controller(this)
    fs.writeFileSync(absolute, content
      .replace(/[/]+ <-/g, '')
      .replace(/\n{3,}/g, '\n\n')
    )
    return this
  }

  /**
   * 写入 Service
   *
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeService(templateName) {
    const {Service} = await import(`../${templateName}`)
    const filename = `business/${this.names.pkgName}/service/${this.names.JavaName}Service.java`
    const absolute = Paths.resolve(this.output, filename).mkdirsParent().absolute()
    console.log(absolute)
    const content = await Service(this)
    fs.writeFileSync(absolute, content
      .replace(/[/]+ <-/g, '')
      .replace(/\n{3,}/g, '\n\n')
    )
    return this
  }

  /**
   * 写入 Repository
   *
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeRepository(templateName) {
    const {Repository} = await import(`../${templateName}`)
    const filename = `business/${this.names.pkgName}/dao/${this.modeNameLowerCase}/${this.names.JavaName}Repository.java`
    const absolute = Paths.resolve(this.output, filename).mkdirsParent().absolute()
    console.log(absolute)
    const content = await Repository(this)
    fs.writeFileSync(absolute, content
      .replace(/[/]+ <-/g, '')
      .replace(/\n{3,}/g, '\n\n')
    )
    return this
  }

  /**
   * 写入 JS Entity
   *
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeJsEntity(templateName) {
    const {JsEntity} = await import(`../${templateName}`)
    const output = this.output.replace(/(\/src\/test\/java)\/.*$/, '$1')
    const filename = `gulp/src/api/entity/${this.names.JavaName}.js`
    const absolute = Paths.resolve(output, filename).mkdirsParent().absolute()
    console.log(absolute)
    const content = await JsEntity(this)
    fs.writeFileSync(absolute, content
      .replace(/[/]+ <-/g, '')
      .replace(/\n{3,}/g, '\n\n')
    )
    return this
  }

  /**
   * 写入 JS Service
   *
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeJsService(templateName) {
    const {JsService} = await import(`../${templateName}`)
    const output = this.output.replace(/(\/src\/test\/java)\/.*$/, '$1')
    const filename = `gulp/src/api/${templateName.startsWith('Open') ? 'Open' : ''}${this.names.JavaName}Service.js`
    const absolute = Paths.resolve(output, filename).mkdirsParent().absolute()
    console.log(absolute)
    const content = await JsService(this, templateName.startsWith('Open') ? 'Open' : '')
    fs.writeFileSync(absolute, content
      .replace(/[/]+ <-/g, '')
      .replace(/\n{3,}/g, '\n\n')
    )
    return this
  }

  /**
   * 写入 JS Service 测试类
   *
   * @param templateName {string} 模板目录
   * @return {Table}
   */
  async writeJsServiceTest(templateName) {
    const {JsServiceTest} = await import(`../${templateName}`)
    const output = this.output.replace(/(\/src\/test\/java)\/.*$/, '$1')
    const filename = `gulp/test/api/${templateName.startsWith('Open') ? 'Open' : ''}${this.names.JavaName}Service.test.js`
    const absolute = Paths.resolve(output, filename).mkdirsParent().absolute()
    console.log(absolute)
    const content = await JsServiceTest(this, templateName.startsWith('Open') ? 'Open' : '')
    fs.writeFileSync(absolute, content
      .replace(/[/]+ <-/g, '')
      .replace(/\n{3,}/g, '\n\n')
    )
    return this
  }
}
